import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Scanner;

public class PullFileMain {
    private static final String IP_V4_ADDRESS_REGEX = "^(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])$";

        private static final String ADB_PATH = ".\\platform-tools\\adb.exe";

    private static final ArrayList<Process> processes = new ArrayList<>();

    private static String currentDir = null;

    private static boolean needWait = false;

    private static boolean isExit = false;

    private static String deviceSerial;

    public static void main(String[] args) {
        String os = System.getenv("OS");
        printMsg("当前操作系统：" + os);

        if (!os.toLowerCase().startsWith("windows")) {
            printMsg("请使用Windows系统运行本程序！");
            return;
        }
        Scanner scanner = new Scanner(System.in);
        Runtime runtime = Runtime.getRuntime();
        int result = listAndSelectDevices(runtime, scanner);
        switch (result) {
            case ConstantsCode.SUCCESS:
                break;
            case ConstantsCode.EXIT:
            case ConstantsCode.ERROR:
            case ConstantsCode.FAIL:
            default:
                exit();
                return;
        }
        listCurrentFiles(runtime, scanner);
        exit();
    }

    /**
     * 列出当前目录下的文件
     *
     * @param runtime Runtime对象
     * @param scanner Scanner对象
     */
    private static int listCurrentFiles(Runtime runtime, Scanner scanner) {

        while (true) {
            if (needWait) {
                continue;
            }
            if (isExit) {
                break;
            }
            ArrayList<String> strings = listCurrentFilesString(runtime);
            if (strings == null) {
                break;
            }
            needWait = true;
            selectFile(runtime, scanner, strings);
        }
        return ConstantsCode.EXIT;
    }

    /**
     * 选择文件
     *
     * @param runtime Runtime对象
     * @param scanner Scanner对象
     * @param strings 文件列表
     */
    private static void selectFile(Runtime runtime, Scanner scanner, ArrayList<String> strings) {
        printMsg("请选择文件：");
        for (int i = 0; i < strings.size(); i++) {
            printMsg(i + " " + strings.get(i));
        }
        printErrMsg("当前文件路径：" + currentDir);
        printMsg("请输入序号：（exit 或 quit 退出程序）");
        while (true) {
            String input = scanner.nextLine();
            int index;
            try {
                index = Integer.parseInt(input);
            } catch (NumberFormatException e) {
                if (input.equals("exit") || input.equals("quit")) {
                    isExit = true;
                    needWait = false;
                    break;
                }
                printErrMsg("输入的不是数字，请重新输入：");
                printMsg("请输入序号：（exit 或 quit 退出程序）");
                continue;
            }
            if (index >= 0 && index < strings.size()) {
                if (index == 0) {
                    currentDir = "";
                    needWait = false;
                    break;
                } else if (index == 1) {
                    if (currentDir == null || currentDir.isEmpty()) {
                        needWait = false;
                        break;
                    }
                    boolean contains = currentDir.contains("/");
                    if (contains) {
                        currentDir = currentDir.substring(0, currentDir.lastIndexOf("/"));
                    } else {
                        currentDir = "";
                    }
                    needWait = false;
                    break;
                }
                String fileName = strings.get(index);
                printMsg("选择的文件是：" + fileName);
                handleSelectFile(runtime, scanner, fileName);
                break;
            } else {
                printErrMsg("输入的序号不在范围内！");
                printMsg("请输入序号：（exit 或 quit 退出程序）");
            }
        }
    }

    /**
     * 处理选择的文件
     *
     * @param runtime  Runtime对象
     * @param scanner  Scanner对象
     * @param fileName 文件名
     */
    private static void handleSelectFile(Runtime runtime, Scanner scanner, String fileName) {
        // 如果是文件夹，则进入文件夹
        if (fileName.startsWith("d")) {
            handleSelectDirFile(runtime, scanner, fileName);
        }
        // 如果是文件，则拉取文件
        else if (fileName.startsWith("-")) {
            handleSelectTargetFile(runtime, scanner, fileName);
        }
        // 如果是链接类型
        else if (fileName.startsWith("l")) {
            handleSelectLinkFile(fileName);
        }
    }

    /**
     * 处理选择的链接类型文件
     *
     * @param fileName 文件名
     */
    private static void handleSelectLinkFile(String fileName) {
        String realFileName = getRealFileName(fileName);
        printMsg("realFileName：" + realFileName);
        currentDir = realFileName;
        if (currentDir.startsWith("/")) {
            currentDir = currentDir.substring(1);
        }
        needWait = false;
    }

    /**
     * 处理选择的文件
     *
     * @param runtime  Runtime对象
     * @param scanner  Scanner对象
     * @param fileName 文件名
     */
    private static void handleSelectTargetFile(Runtime runtime, Scanner scanner, String fileName) {
        String realFileName = getRealFileName(fileName);
        if (currentDir == null || currentDir.isEmpty()) {
            currentDir = realFileName;
        } else {
            currentDir = currentDir + "/" + realFileName;
        }
        printMsg("选中了文件：" + currentDir);
        printMsg("请输入需要拉取到的目标路径（可拖放）(exit或quit退出)：");
        String input = scanner.nextLine();
        if (input.equals("exit") || input.equals("quit")) {
            return;
        }
        if (!input.endsWith("\\")) {
            input += "\\";
        }
        printMsg("拉取到的目标路径是：" + input);
        String cmd = ADB_PATH + " -s " + deviceSerial + " pull " + currentDir + " " + input;
        printMsg("执行命令：" + cmd);
        try {
            Process process = runtime.exec(cmd);
            process.waitFor();
            InputStream inputStream = process.getInputStream();
            byte[] bytes = new byte[inputStream.available()];
            int read = inputStream.read(bytes);
            if (read < 0) {
                printMsg("拉取文件失败！");
            }
            String result = new String(bytes);
            printMsg(result);
            printMsg("拉取完成！");
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理选择的文件夹
     *
     * @param runtime  Runtime对象
     * @param scanner  Scanner对象
     * @param fileName 文件名
     */
    private static void handleSelectDirFile(Runtime runtime, Scanner scanner, String fileName) {
        printMsg("请选择操作：1 进入文件夹;2 拉取此文件夹;3 仅拉取此文件夹中的文件（不包含子文件夹）;exit/quit 退出程序");
        boolean needBreak = false;
        String realFileName = getRealFileName(fileName);
        while (!needBreak) {
            String line = scanner.nextLine();
            switch (line) {
                case "1" -> {
                    printMsg("进入文件夹：" + realFileName);
                    if (currentDir == null || currentDir.isEmpty()) {
                        currentDir = realFileName;
                    } else {
                        currentDir = String.format("%s/%s", currentDir, realFileName);
                    }
                    needWait = false;
                    needBreak = true;
                }
                case "2" -> {
                    printMsg("拉取此文件夹：" + realFileName);
                    if (currentDir == null || currentDir.isEmpty()) {
                        currentDir = realFileName;
                    } else {
                        currentDir = currentDir + "/" + realFileName;
                    }
                    pullDir(runtime, scanner);
                    isExit = true;
                    needWait = false;
                    needBreak = true;
                }
                case "3" -> {
                    printMsg("拉取此文件夹下的文件：" + realFileName);
                    if (currentDir == null || currentDir.isEmpty()) {
                        currentDir = realFileName;
                    } else {
                        currentDir = currentDir + "/" + realFileName;
                    }
                    pullFilesInDir(runtime, scanner);
                    isExit = true;
                    needWait = false;
                    needBreak = true;
                }
                case "exit", "quit" -> {
                    printMsg("退出！");
                    isExit = true;
                    needWait = false;
                    needBreak = true;
                }
                default ->
                        printMsg("请选择操作：1 进入文件夹;2 拉取此文件夹;3 仅拉取此文件夹中的文件（不包含子文件夹）;exit/quit 退出程序");
            }
        }
    }

    private static void pullFilesInDir(Runtime runtime, Scanner scanner) {
        printMsg("选中文件夹：" + currentDir);
        printMsg("请输入拉取到的目标的路径（可拖放）(exit或quit退出)：");
        String input = scanner.nextLine();
        if (input.equals("exit") || input.equals("quit")) {
            return;
        }
        if (!input.endsWith("\\")) {
            input += "\\";
        }
        printMsg("拉取到的目标路径是：" + input);
        ArrayList<String> strings = listCurrentFilesString(runtime);
        if (strings == null || strings.size() == 0) {
            printMsg("没有可拉取的文件！");
            return;
        }
        strings.remove(0);
        strings.remove(0);
        for (String fileName : strings) {
            String realFileName = getRealFileName(fileName);
            if (fileName.startsWith("-")) {
                String targetFileName;
                if (currentDir == null || currentDir.isEmpty()) {
                    targetFileName = realFileName;
                } else {
                    targetFileName = currentDir + "/" + realFileName;
                }
                printMsg("拉取文件：" + targetFileName);
                String cmd = ADB_PATH + " -s " + deviceSerial + " pull " + targetFileName + " " + input;
                printMsg("执行命令：" + cmd);
                try {
                    Process process = runtime.exec(cmd);
                    process.waitFor();
                    InputStream inputStream = process.getInputStream();
                    byte[] bytes = new byte[inputStream.available()];
                    int read = inputStream.read(bytes);
                    if (read < 0) {
                        printMsg("拉取文件失败！");
                    }
                    String result = new String(bytes);
                    printMsg(result);
                } catch (IOException | InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                printMsg(realFileName + " 不是文件，跳过！");
            }
        }
        isExit = true;
        needWait = false;
    }

    private static String getRealFileName(String fileName) {
        String[] split = fileName.split(" ");
        return split[split.length - 1].trim();
    }

    /**
     * 拉取全部文件
     *
     * @param runtime Runtime对象
     * @param scanner Scanner对象
     */
    private static void pullDir(Runtime runtime, Scanner scanner) {
        printMsg("选中文件夹：" + currentDir);
        printMsg("请输入拉取到的目标的路径（可拖放）(exit或quit退出)：");
        String input = scanner.nextLine();
        if (input.equals("exit") || input.equals("quit")) {
            return;
        }
        if (!input.endsWith("\\")) {
            input += "\\";
        }
        printMsg("拉取到的目标路径是：" + input);
        String cmd = ADB_PATH + " -s " + deviceSerial + " pull " + currentDir + " " + input;
        Process process;
        try {
            process = runtime.exec(cmd);
            InputStream inputStream = process.getInputStream();
            ArrayList<Byte> cache = new ArrayList<>();
            byte[] bytes = new byte[1024];
            while (true) {
                int read = inputStream.read(bytes);
                if (read < 0) {
                    break;
                }
                for (int i = 0; i < read; i++) {
                    cache.add(bytes[i]);
                }
            }
            byte[] resultBytes = new byte[cache.size()];
            for (int i = 0; i < resultBytes.length; i++) {
                resultBytes[i] = cache.get(i);
            }
            String result = new String(resultBytes);
            printMsg(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
        isExit = true;
        needWait = false;
    }

    private static ArrayList<String> listCurrentFilesString(Runtime runtime) {
        String command = getCurrentDirCommand();
        printMsg("执行命令：" + command);
        try {
            Process process = runtime.exec(command);
            InputStream inputStream = process.getInputStream();
            ArrayList<Byte> cache = new ArrayList<>();
            byte[] bytes = new byte[1024];
            while (true) {
                int read = inputStream.read(bytes);
                if (read < 0) {
                    break;
                }
                for (int i = 0; i < read; i++) {
                    cache.add(bytes[i]);
                }
            }
            byte[] resultBytes = new byte[cache.size()];
            for (int i = 0; i < resultBytes.length; i++) {
                resultBytes[i] = cache.get(i);
            }
            String result = new String(resultBytes);
            String[] split = result.split("\r\n");
            ArrayList<String> strings = new ArrayList<>();
            for (String s : split) {
                if (s.startsWith("total ")) {
                    continue;
                }
                if (s.endsWith(".")) {
                    continue;
                }
                if (s.endsWith("..")) {
                    continue;
                }
                strings.add(s);
            }
            strings.add(0, "回到首页");
            strings.add(1, "上一页");
            return strings;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String getCurrentDirCommand() {
        String cmd;
        if (currentDir == null || currentDir.isEmpty()) {
            cmd = ADB_PATH + " -s " + deviceSerial + " shell ls -al\n";
        } else {
            cmd = ADB_PATH + " -s " + deviceSerial + " shell ls -al " + currentDir + "\n";
        }
        return cmd;
    }

    /**
     * 退出程序
     */
    private static void exit() {
        for (Process process : processes) {
            if (process.isAlive()) {
                process.destroy();
            }
        }
        printMsg("程序已经退出");
        System.exit(0);
    }

    /**
     * 列出全部已经连接的设备
     *
     * @param runtime 当前运行环境
     * @param scanner 控制台输入流
     */
    private static int listAndSelectDevices(Runtime runtime, Scanner scanner) {
        try {
            Process process = runtime.exec(ADB_PATH + " devices");
            processes.add(process);
            int waitFor = process.waitFor();
            if (waitFor != 0) {
                printMsg("命令运行失败！");
                return ConstantsCode.ERROR;
            }
            InputStream inputStream = process.getInputStream();
            byte[] bytes = new byte[inputStream.available()];
            int read = inputStream.read(bytes);
            if (read < 0) {
                printMsg("获取设备列表失败！");
                return ConstantsCode.ERROR;
            }
            String result = new String(bytes);
            result = result.replaceAll("\r", "");
            printMsg(result);
            String[] split = result.split("\n");
            ArrayList<String> devices = clearInvalidLine(split);
            printMsg("计算机上已经连接的设备数量：" + devices.size());
            if (devices.isEmpty()) {
                printMsg("没有已经连接的设备,请连接设备！\n\n");
                return connectDeviceByNetwork(runtime, scanner);
            }
            return selectDevice(runtime, devices, scanner);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
            printMsg("找不到adb.exe，请确认adb已经安装到系统环境变量中！");
            return ConstantsCode.ERROR;
        }
    }

    private static int selectDevice(Runtime runtime, ArrayList<String> devices, Scanner scanner) {
        printMsg("已经连接的设备如下：");
        for (int i = 0; i < devices.size(); i++) {
            printMsg((i + 1) + ": " + devices.get(i));
        }
        printMsg("请输入要连接的设备序号(输入exit或quit退出程序,输入network连接一个新的设备)：");
        int index;
        while (true) {
            boolean nextLine = scanner.hasNextLine();
            if (!nextLine) {
                continue;
            }
            String s = scanner.nextLine().trim();
            if (s.isEmpty()) {
                printMsg("请输入要连接的设备序号(输入exit或quit退出程序,输入network连接一个新的设备)：");
                continue;
            }

            if (s.equalsIgnoreCase("exit") || s.equalsIgnoreCase("quit")) {
                return ConstantsCode.EXIT;
            } else if (s.equalsIgnoreCase("network")) {
                return connectDeviceByNetwork(runtime, scanner);
            }

            try {
                int indexFromInput = Integer.parseInt(s);
                int realIndex = indexFromInput - 1;
                if (realIndex < 0 || realIndex > devices.size() - 1) {
                    printMsg("请输入正确的序号(输入exit或quit退出程序,输入network连接一个新的设备)：");
                    continue;
                }
                index = realIndex;
            } catch (NumberFormatException e) {
                printMsg("请输入正确的序号(输入exit或quit退出程序)：");
                continue;
            }
            break;
        }
        String s = devices.get(index);
        String deviceSerial = s.split("\t")[0];
        String deviceName = s.split("\t")[1];
        if (deviceName.isEmpty()) {
            deviceName = deviceSerial;
        }
        printMsg("选择的目标设备序列号（或IP地址）：" + deviceSerial + "设备名称：" + deviceName);
        if (deviceName.equalsIgnoreCase("offline")) {
            printMsg("设备不在线，请断开后重连该设备，或尝试重启该设备！");
            return ConstantsCode.FAIL;
        }
        PullFileMain.deviceSerial = deviceSerial;
        return ConstantsCode.SUCCESS;
    }

    private static int connectDeviceByNetwork(Runtime runtime, Scanner scanner) {
        printMsg("如果您的设备可用网络进行连接，请输入目标设备IP地址(输入exit或quit退出程序)：");
        String ip;
        while (true) {
            boolean nextLine = scanner.hasNextLine();
            if (!nextLine) {
                continue;
            }
            String s = scanner.nextLine().trim();
            if (s.isEmpty()) {
                printMsg("请输入目标设备IP地址(输入exit或quit退出程序)：");
                continue;
            }
            boolean valid = isIp4Valid(s);
            if (!valid) {
                if (s.equalsIgnoreCase("exit") || s.equalsIgnoreCase("quit")) {
                    return ConstantsCode.EXIT;
                }
                printMsg("请输入正确的IP地址(输入exit或quit退出程序)：");
                continue;
            }
            ip = s;
            break;
        }
        printMsg("正在连接目标设备，请耐心等待...");
        try {
            Process process = runtime.exec(ADB_PATH + " connect " + ip);
            processes.add(process);
            process.waitFor();
            InputStream inputStream = process.getInputStream();
            byte[] bytes = new byte[inputStream.available()];
            int read = inputStream.read(bytes);
            if (read < 0) {
                printMsg("命令执行结果获取失败！");
                return ConstantsCode.ERROR;
            }
            String s = new String(bytes, StandardCharsets.UTF_8);
            printMsg(s);
            PrintWriter writer = new PrintWriter(new StringWriter());
            writer.println();
            if (!s.toLowerCase().startsWith("connected to") && !s.toLowerCase().startsWith("already connected to")) {
                printMsg("与目标设备连接失败！");
                return ConstantsCode.FAIL;
            }
            deviceSerial = ip;
            return ConstantsCode.SUCCESS;
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
            printMsg("命令执行失败，请检查运行环境是否正常");
            return ConstantsCode.ERROR;
        }
    }

    private static ArrayList<String> clearInvalidLine(String[] split) {
        ArrayList<String> devices = new ArrayList<>();
        for (String s : split) {
            String cache = s.trim();
            if (cache.isEmpty()) {
                continue;
            }
            if (cache.equalsIgnoreCase("List of devices attached")) {
                continue;
            }
            devices.add(cache);
        }
        return devices;
    }

    private static void printMsg(String x) {
        System.out.println(x);
    }

    private static void printErrMsg(String x) {
        System.err.println(x);
    }

    /**
     * 判断IPV4地址是否合法
     *
     * @param ip IPV4地址
     * @return true：合法；false：不合法
     */
    private static boolean isIp4Valid(String ip) {
        String[] split = ip.split(":");
        if (split.length == 1) {
            return ip.matches(IP_V4_ADDRESS_REGEX);
        } else if (split.length == 2) {
            return split[0].matches(IP_V4_ADDRESS_REGEX) && isValidPort(split[1]);
        } else {
            return false;
        }
    }

    /**
     * 判断端口号是否合法
     *
     * @param portString 端口号字符串
     * @return true：合法；false：不合法
     */
    private static boolean isValidPort(String portString) {
        try {
            int port = Integer.parseInt(portString);
            return port >= 0 && port <= 65535;
        } catch (NumberFormatException e) {
            return false;
        }
    }
}
